import Cesium, { AssociativeArray, Viewer } from '../Ces/Cesium'
import WeImageryLayer from '../Display/WeImageryLayer';
import WeVectorImageryLayer from '../Display/WeVectorImageryLayer';
import DetectiveLineLayer from './DetectiveLineLayer';
import WeViewExtentEvent from '../Display/WeViewExtentEvent';
import WeContext from '../Core/WeContext';

class LayerManager {
  private _viewExtentEvent: WeViewExtentEvent
  private _detectiveLineLayer: DetectiveLineLayer
  private _shouldAnimate = false
  private viewer: Viewer
  private _layers: AssociativeArray
  private _weContext: WeContext = new WeContext()
  private _loadingRef: number = 0
  private _submitRef: number = 0
  private _dataLoadedCallback: any[] = []
  constructor(viewer) {
    this.viewer = viewer;
    this._layers = new Cesium.AssociativeArray()
  }

  addDataLoadedCallback(callback) {
    this._dataLoadedCallback.push(callback)
  }

  private _dataLoaded() {
    this._dataLoadedCallback.forEach(e => {
      e(this)
    })
  }

  get taskProcessorPool() {
    return this._weContext.taskProcessorPool
  }

  get isLoading() {
    return this._loadingRef > 0 || this._submitRef > 0
  }

  get loadingEvent() {
    return this._weContext.loadingEvent
  }

  get context(): WeContext {
    return this._weContext
  }

  get viewExtentEvent(): WeViewExtentEvent {
    if (!Cesium.defined(this._viewExtentEvent)) {
      this._viewExtentEvent = new WeViewExtentEvent(this.viewer.scene)
    }
    return this._viewExtentEvent
  }

  get shouldAnimate() {
    return this._shouldAnimate
  }
  set shouldAnimate(value) {
    this._shouldAnimate = value
  }

  get showDetectiveLine() {
    return Cesium.defined(this._detectiveLineLayer)
  }
  set showDetectiveLine(value) {
    if (value) {
      if (!Cesium.defined(this._detectiveLineLayer)) {
        this._detectiveLineLayer = this.viewer.scene.groundPrimitives.add(new DetectiveLineLayer())
      }
    } else if (Cesium.defined(this._detectiveLineLayer)) {
      this.viewer.scene.groundPrimitives.remove(this._detectiveLineLayer)
      this._detectiveLineLayer = undefined;
    }
  }

  get weImageryLayers() {
    return this._layers.values.filter(e => e instanceof WeImageryLayer)
  }

  getById(id) {
    if (!Cesium.defined(id)) {
      throw new Cesium.DeveloperError('id is required.')
    }

    return this._layers.get(id)
  }

  remove(id) {
    const layer = this.getById(id)

    if (!Cesium.defined(layer)) return

    const { viewer } = this
    if (layer instanceof Cesium.ImageryLayer) {
      viewer.imageryLayers.remove(layer)
    } else if (layer instanceof Cesium.Cesium3DTileset
    ) {
      viewer.scene.primitives.remove(layer)
    } else if (layer instanceof Cesium.PostProcessStage) {
      viewer.scene.postProcessStages.remove(layer);
    } else if (layer instanceof Cesium.KmlDataSource
      || layer instanceof Cesium.GeoJsonDataSource
      || layer instanceof Cesium.CustomDataSource) {
      layer.destroy?.()
      viewer.dataSources.remove(layer)
    }

    this._layers.remove(id)

    return true;
  }

  raiseToTop(id) {
    const imagrylayer = this.getById(id)

    if (imagrylayer instanceof Cesium.ImageryLayer) {
      this.viewer.imageryLayers.raiseToTop(imagrylayer)
    }
  }

  addTilesetLayer = async function (options, id) {
    if (!Cesium.defined(id)) {
      id = Cesium.createGuid()
    }

    const layers = this._layers;

    if (layers.contains(id)) {
      throw new Cesium.RuntimeError(
        'An layer with id ' + id + ' already exists in this collection.',
      )
    }

    const layer = options instanceof Cesium.Cesium3DTileset ?
      options :
      await Cesium.Cesium3DTileset.fromUrl(options.url, options)

    layer.style = options.style;
    layers.set(id, layer)

    this.viewer.scene.primitives.add(layer)

    if (!(options.enablePick ?? false)) {
      const update = layer.update
      layer.update = (frameState) => {
        if (!frameState.passes.render) return
        update.bind(layer)(frameState)
      }
    }

    return layer;
  }

  addWeVectorImageryLayer(options, id: string) {
    if (!Cesium.defined(id)) {
      id = Cesium.createGuid()
    }

    const layers = this._layers;

    if (layers.contains(id)) {
      throw new Cesium.RuntimeError(
        'An layer with id ' + id + ' already exists in this collection.',
      )
    }

    const { viewer } = this;

    const layer = new WeVectorImageryLayer({
      ...options,
    })

    layer.id = id;
    //@ts-ignore
    viewer.imageryLayers.add(layer)

    layers.set(id, layer)

    return layer;
  }

  addWeImageryLayer(options, id: string) {
    if (!Cesium.defined(id)) {
      id = Cesium.createGuid()
    }

    const layers = this._layers;

    if (layers.contains(id)) {
      throw new Cesium.RuntimeError(
        'An layer with id ' + id + ' already exists in this collection.',
      )
    }

    const layer = new WeImageryLayer({
      ...options,
      mgr: this,
    })

    layer.id = id;
    this.viewer.imageryLayers.add(layer as any)
    layers.set(id, layer)

    return layer;
  }

  addImageryProvider(provider, id) {
    if (!Cesium.defined(id)) {
      id = Cesium.createGuid()
    }

    const layers = this._layers;

    if (layers.contains(id)) {
      throw new Cesium.RuntimeError(
        'An layer with id ' + id + ' already exists in this collection.',
      )
    }
    const imagrylayer = this.viewer.imageryLayers.addImageryProvider(provider)

    if (provider._rectangle) {
      //@ts-ignore
      imagrylayer._rectangle = provider._rectangle;
    }
    layers.set(id, imagrylayer)

    return imagrylayer;
  }

  addCustomDataSource(id) {
    const { viewer } = this;

    const layers = this._layers;

    if (layers.contains(id)) {
      throw new Cesium.RuntimeError(
        'An layer with id ' + id + ' already exists in this collection.',
      )
    }
    const dataSource = new Cesium.CustomDataSource(id)

    layers.set(id, dataSource)

    //@ts-ignore
    viewer.dataSources._dataSources.push(dataSource)
    //@ts-ignore
    viewer.dataSources._dataSourceAdded.raiseEvent(viewer.dataSources, dataSource)

    if (!dataSource.destroy) dataSource.destroy = function () { }

    return dataSource;
  }

  addGeoJsonDataSource(url, options, id) {
    const that = this;

    if (!id) {
      id = options.id ?? Cesium.createGuid()
    }

    const layers = that._layers;
    if (layers.contains(id)) {
      throw new Cesium.RuntimeError(
        'An layer with id ' + id + ' already exists in this collection.',
      )
    }

    const promise = Cesium.GeoJsonDataSource.load(url, options)

    promise.then((dataSource) => {
      dataSource.name = id;
      dataSource.allowPicking = options.allowPicking ?? true
      dataSource.allowHighLight = options.allowHighLight ?? true
      that.viewer.dataSources.add(dataSource)
      layers.set(id, dataSource)
    })

    return promise;
  }

  addCZMLLayer(url, options) {
    const that = this;

    const promise = Cesium.CzmlDataSource.load(url, options)

    promise.then((dataSource) => {
      if (!Cesium.defined(options.id)) {
        options.id = Cesium.createGuid()
      }

      that.viewer.dataSources.add(dataSource)

      const id = options.id;
      const layers = that._layers;

      if (layers.contains(id)) {
        throw new Cesium.RuntimeError(
          'An layer with id ' + id + ' already exists in this collection.',
        )
      }
      layers.set(id, dataSource)
    })

    return promise;
  }

  addKMLLayer(url, options) {
    const that = this;

    const promise = Cesium.KmlDataSource.load(
      url,
      Object.assign(options, {
        camera: this.viewer.scene.camera,
        canvas: this.viewer.scene.canvas,
      }),
    )

    promise.then((dataSource) => {
      if (!Cesium.defined(options.id)) {
        options.id = Cesium.createGuid()
      }
      that.viewer.dataSources.add(dataSource)

      const id = options.id;
      const layers = that._layers;

      if (layers.contains(id)) {
        throw new Cesium.RuntimeError(
          'An layer with id ' + id + ' already exists in this collection.',
        )
      }
      layers.set(id, dataSource)
    })

    return promise;
  }

  addPostProcessStage(processState, id) {
    if (!id) id = Cesium.createGuid()
    const layers = this._layers;
    if (layers.contains(id)) {
      throw new Cesium.RuntimeError(
        'An layer with id ' + id + ' already exists in this collection.',
      )
    }

    if (!(processState instanceof Cesium.PostProcessStage)) {
      processState = new Cesium.PostProcessStage(processState)
    }

    const execute = processState.execute
    processState.execute = (context, colorTexture, depthTexture, idTexture) => {
      if (context.uniformState.globeDepthTexture) {
        execute.bind(processState)(context, colorTexture, depthTexture, idTexture)
      }
    }

    layers.set(id, processState)

    const { viewer } = this
    viewer.scene.postProcessStages.add(processState);
  }

  update(frameState) {
    if (this._weContext) {
      this._weContext.update(frameState)
    }
    if (this._viewExtentEvent) {
      this._viewExtentEvent.update(frameState)
    }

    frameState.loadingRef = +this._loadingRef
    frameState.submitRef = +this._submitRef

    let loadingRef = 0
    let submitRef = 0

    let i = 0;

    for (; i < this._layers.length; i++) {
      const layer = this._layers.values[i];

      if (Cesium.defined(layer.update)) {
        if (layer instanceof WeImageryLayer) {
          loadingRef += layer.loadingRef
          submitRef += layer.submitRef
        }
        layer.update(frameState)
      }
    }

    this._loadingRef = loadingRef
    this._submitRef = submitRef

    if (frameState.submitRef > 0 && submitRef == 0) {
      this._dataLoaded()
    }

    if (this._shouldAnimate) {
      this.viewer.clock.shouldAnimate = !this.isLoading;
    }
  }

  isDestroyed() {
    return false
  }

  destroy() {
    this._viewExtentEvent = this._viewExtentEvent && this._viewExtentEvent.destroy()
    const layers = this._layers
    //@ts-ignore
    Object.keys(layers._hash).forEach((id) => {
      this.remove(id)
    })
    this._weContext = this._weContext && this._weContext.destroy()
    return Cesium.destroyObject(this)
  }
}



export default LayerManager;
